#ifndef ___backup_server_connection_h___
#define ___backup_server_connection_h___

#include <exception>
#include <string>
#include <vector>

#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>

#include <boost/utility.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>

/**
 * This class is an interface to the backup http server.
 * The backup_server_connection uses a thread pool to
 * process asynchronous downloads.
 */
class backup_server_connection : public boost::noncopyable
{
public:
    /**
     * Contains data corresponding to the custom http
     * header used when communicating with the server.
     */
    struct request {

        /**
         * @param ps The piece size, i.e size of each torrent piece.
         * @param idx Piece index
         * @param c The number of contiguous pieces to download, starting at idx.
         */
        request(const size_t& ps,
                const size_t& idx,
                const size_t& c)
        : piece_size(ps), 
          index(idx), 
          count(c)
        {
        }

        size_t piece_size;
        size_t index;
        size_t count;
    };

    // callback function signature
    typedef boost::function<void(request, std::vector<char*>)> response_handler_fun;
   
    /**
     * Thrown if the internal cURL instance couldn't be initialized.
     */
    struct curl_init_exception : public std::exception
    {
        virtual const char* what() const throw()
        {
            return "Failed to initialize curl";
        }
    };

    /**
     * Creates a new backup_server_connection object and sets up the internal
     * thread pool.
     *
     * @param address The address of the server.
     * @param port The tcp port number used by the server.
     * @param file The name of the file to request pieces from.
     * @param max_simultaneous_downloads The number of threads in the thread pool.
     */
    backup_server_connection(const std::string& address,
                             const std::string& port,
                             const std::string& file,
                             size_t max_simultaneous_downloads);

    ~backup_server_connection();

    /**
     * Sends a request to the backup server. It's safe to call this function
     * from multiple threads.
     *
     * @param req The request header parameters.
     * @param response_handler The callback function for handling completed downloads.
     *
     */
    void send(const request& req, response_handler_fun response_handler);

private:

    struct buffer_wrapper : public boost::noncopyable
    {
        buffer_wrapper(size_t size)
            : buf_size(size), bytes_written(0)
        {
            buffer = new char[buf_size];
        }

        ~buffer_wrapper()
        {
            delete [] buffer;
        }

        char * buffer;
        size_t buf_size;
        size_t bytes_written;
    };

    struct curl_instance
    {
        curl_instance(const std::string& connection_string)
        {
            curl = curl_easy_init();
            
            if(!curl) { //FIXME: how to handle this?
                throw curl_init_exception();
            }

            curl_easy_setopt(curl, CURLOPT_URL, connection_string.c_str());
            curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);

            curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);

            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);

        }
        
        ~curl_instance()
        {
            curl_easy_cleanup(curl);
        }
 
        // handle to curl
        CURL *curl;
        
        // libcurl callback function
        static size_t write_data(void * buffer, 
                                 size_t size, 
                                 size_t nmemb, 
                                 void *userp);

    };

    // worker thread function
    void worker(std::string connection_str, 
                request req,
                response_handler_fun response_handler);


    std::string connection_string;
    

    // 'work' needs io_service for init. 
    // re-ordering these two will break the code!
    boost::asio::io_service io_service;
    boost::asio::io_service::work work;

    // pointers to threads in the thread pool
    std::vector<boost::shared_ptr<boost::thread> > threads;

    // this gives us one curl instance per thread in the pool
    boost::thread_specific_ptr<curl_instance> curl_ptr;

};

#endif // ___backup_server_connection_h___
